/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/daikit4gxt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxt.client.ui.popup;

import com.daikit.commons.shared.bean.AbstractDkBeanWithId;
import com.daikit.commons.shared.bean.AbstractDkModifiableBeanWithId;
import com.daikit.commons.shared.exception.DkUnsupportedMethodException;
import com.daikit.daikit4gxt.client.DkBaseMain;
import com.daikit.daikit4gxt.client.action.DkBaseAction;
import com.daikit.daikit4gxt.client.action.DkBaseAsyncCallback;
import com.daikit.daikit4gxt.client.constants.DkErrorCodes;
import com.daikit.daikit4gxt.client.editor.DkClearValidationVisitor;
import com.daikit.daikit4gxt.client.editor.DkPreFlushVisitor;
import com.daikit.daikit4gxt.client.editor.DkSimpleBeanEditorDriver;
import com.daikit.daikit4gxt.client.log.DkLogger;
import com.daikit.daikit4gxt.client.ui.UIInvalidatable;
import com.daikit.daikit4gxt.client.utils.miscs.DkStoreUtils;
import com.daikit.daikit4gxt.client.utils.miscs.TreeStoreModifierCallback;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.user.client.Timer;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.TreeStore;


/**
 * A class to extend for Dialog box for edition or creation of bean
 *
 * @author tcaselli
 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
 * @param <BEANTYPE>
 *           edited bean type
 * @param <EDITORTYPE>
 *           {@link Editor} type (editor for type B)
 * @param <STORETYPE>
 *           the type of the grid to which the bean should then bean added or updated
 */
public abstract class DkEditCreateProxyPopup<BEANTYPE extends AbstractDkBeanWithId, STORETYPE extends AbstractDkBeanWithId, EDITORTYPE extends Editor<BEANTYPE>>
		extends DkFormDialog implements UIInvalidatable
{

	protected DkLogger log = DkLogger.getLog(getClass());

	private ListStore<? super STORETYPE> store;
	private TreeStore<STORETYPE> treeStore; // only one of store and treeStore is useful at the same time
	private final EDITORTYPE editor;
	private boolean isCreation = false;
	private BEANTYPE editedModel;
	private STORETYPE inputModel;
	private boolean hideAfterSave = true;

	protected STORETYPE treeParentModel;
	protected int index = 0;
	protected DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback;

	@SuppressWarnings("rawtypes")
	private DkSimpleBeanEditorDriver driver;

	protected DkEditCreateProxyPopup(final EDITORTYPE editor)
	{
		this(editor, 1);
	}

	protected DkEditCreateProxyPopup(final EDITORTYPE editor, final int numberOfColumns)
	{
		super(false, numberOfColumns, DisplayableButton.SAVE, DisplayableButton.RESET, DisplayableButton.CANCEL);
		this.editor = editor;
	}

	/**
	 * Use one of these methods instead:
	 * <ul>
	 * <li>{@link #showForUpdate(ListStore, AbstractDkBeanWithId)}</li>
	 * <li>{@link #showForUpdate(TreeStore, AbstractDkBeanWithId)}</li>
	 * <li>
	 * {@link #showForUpdate(ListStore, AbstractDkBeanWithId)}</li>
	 * <li>
	 * {@link #showForUpdate(TreeStore, AbstractDkBeanWithId)}</li>
	 * <li>{@link #showForCreation()}</li>
	 * <li>{@link #showForCreation(ListStore)}</li>
	 * <li>
	 * {@link #showForCreation(DkEditCreatePopupCallback)}</li>
	 * <li>{@link #showForCreation(ListStore, DkEditCreatePopupCallback)}</li>
	 * <li>
	 * {@link #showForCreation(TreeStore, AbstractDkBeanWithId)}</li>
	 * </li>
	 * <li>
	 * {@link #showForCreation(TreeStore, AbstractDkBeanWithId, int)}</li>
	 * </li>
	 * <li>
	 * {@link #showForCreation(TreeStore, AbstractDkBeanWithId, DkEditCreatePopupCallback)}</li>
	 * </li>
	 * <li>
	 * {@link #showForCreation(TreeStore, AbstractDkBeanWithId, int, DkEditCreatePopupCallback)}</li>
	 * </li>
	 * </ul>
	 */
	@Deprecated
	@Override
	public void show()
	{
		throw new DkUnsupportedMethodException(DkErrorCodes.DK4GXT00001, "This method must not be called directly. See javadoc for details.");
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// SHOW FOR UPDATE
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-


	/**
	 * Show the Dialog box for edition of the given model without store, so without possibility to add the created
	 * instance to any grid.
	 *
	 * @param model
	 *           the model to be edited
	 */
	public void showForUpdate(final STORETYPE model)
	{
		showForUpdate((ListStore<? super STORETYPE>) null, model, null);
	}


	/**
	 * Show the Dialog box for edition of the given model without store, so without possibility to add the created
	 * instance to any grid.
	 *
	 * @param model
	 *           the model to be edited
	 * @param callback
	 *           the {@link DkEditCreatePopupCallback} callback
	 */
	public void showForUpdate(final STORETYPE model, final DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback)
	{
		showForUpdate((ListStore<? super STORETYPE>) null, model, callback);
	}

	/**
	 * Show the Dialog box for edition of the given model with no callback
	 *
	 * @param store
	 *           the Store
	 * @param model
	 *           the model to be edited
	 */
	public void showForUpdate(final ListStore<? super STORETYPE> store, final STORETYPE model)
	{
		showForUpdate(store, model, null);
	}

	/**
	 * Show the Dialog box for edition of the given model with no callback
	 *
	 * @param treeStore
	 *           the TreeStore
	 * @param model
	 *           the model to be edited
	 */
	public void showForUpdate(final TreeStore<STORETYPE> treeStore, final STORETYPE model)
	{
		showForUpdate(treeStore, model, null);
	}

	/**
	 * Show the Dialog box for edition of the given model
	 *
	 * @param store
	 *           the Store
	 * @param model
	 *           the model to be edited
	 * @param callback
	 *           the {@link DkEditCreatePopupCallback} callback
	 */
	public void showForUpdate(final ListStore<? super STORETYPE> store, final STORETYPE model,
			final DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback)
	{
		this.store = store;
		this.treeStore = null;
		this.callback = callback;
		getActionRetrieveModelForEdition(model).execute();
	}

	/**
	 * Show the Dialog box for edition of the given model
	 *
	 * @param treeStore
	 *           the TreeStore
	 * @param model
	 *           the model to be edited
	 * @param callback
	 *           the {@link DkEditCreatePopupCallback} callback
	 */
	public void showForUpdate(final TreeStore<STORETYPE> treeStore, final STORETYPE model,
			final DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback)
	{
		this.store = null;
		this.treeStore = treeStore;
		this.callback = callback;
		getActionRetrieveModelForEdition(model).execute();
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// SHOW FOR CREATION
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Show the Dialog box for creation of a new model without store, so without possibility to add the created instance
	 * to any grid.
	 */
	public void showForCreation()
	{
		showForCreation((ListStore<STORETYPE>) null, null);
	}

	/**
	 * Show the Dialog box for creation of a new model without store, so without possibility to add the created instance
	 * to any grid.
	 *
	 * @param callback
	 *           the {@link DkEditCreatePopupCallback} callback
	 */
	public void showForCreation(final DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback)
	{
		showForCreation(null, callback);
	}

	/**
	 * Show the Dialog box for creation of a new model with no callback
	 *
	 * @param store
	 *           the Store
	 */
	public void showForCreation(final ListStore<STORETYPE> store)
	{
		showForCreation(store, null);
	}

	/**
	 * Show the Dialog box for creation of a new model
	 *
	 * @param store
	 *           the Store
	 * @param callback
	 *           the {@link DkEditCreatePopupCallback} callback
	 */
	public void showForCreation(final ListStore<STORETYPE> store, final DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback)
	{
		this.store = store;
		this.treeStore = null;
		this.index = 0;
		this.treeParentModel = null;
		this.callback = callback;
		getActionRetrieveModelForCreation().execute();
	}

	/**
	 * Show the Dialog box for creation of a new model with no save/show callback
	 *
	 * @param treeStore
	 *           the TreeStore
	 * @param treeParent
	 *           the parent model in which the created entry will be added
	 */
	public void showForCreation(final TreeStore<STORETYPE> treeStore, final STORETYPE treeParent)
	{
		showForCreation(treeStore, treeParent, null);
	}

	/**
	 * Show the Dialog box for creation of a new model
	 *
	 * @param treeStore
	 *           the TreeStore
	 * @param treeParent
	 *           the parent model in which the created entry will be added
	 * @param callback
	 *           the {@link DkEditCreatePopupCallback} callback
	 */
	public void showForCreation(final TreeStore<STORETYPE> treeStore, final STORETYPE treeParent,
			final DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback)
	{
		showForCreation(treeStore, treeParent, 0, callback);
	}

	/**
	 * Show the Dialog box for creation of a new model with no save/show callback
	 *
	 * @param treeStore
	 *           the TreeStore
	 * @param treeParent
	 *           the parent model in which the created entry will be added
	 * @param index
	 *           the index where to add the created element within parent
	 */
	public void showForCreation(final TreeStore<STORETYPE> treeStore, final STORETYPE treeParent, final int index)
	{
		showForCreation(treeStore, treeParent, index, null);
	}

	/**
	 * Show the Dialog box for creation of a new model
	 *
	 * @param treeStore
	 *           the TreeStore
	 * @param treeParent
	 *           the parent model in which the created entry will be added
	 * @param index
	 *           the index where to add the created element within parent
	 * @param callback
	 *           the {@link DkEditCreatePopupCallback} callback
	 */
	public void showForCreation(final TreeStore<STORETYPE> treeStore, final STORETYPE treeParent, final int index,
			final DkEditCreatePopupCallback<BEANTYPE, STORETYPE, EDITORTYPE> callback)
	{
		this.store = null;
		this.treeStore = treeStore;
		this.index = index;
		this.treeParentModel = treeParent;
		this.callback = callback;
		getActionRetrieveModelForCreation().execute();
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// RPC
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	protected void callCreateOrUpdateRpc(final BEANTYPE bean, final DkBaseAsyncCallback<BEANTYPE> callback)
	{
		if (isCreation())
		{
			callCreateRpc(bean, callback);
		}
		else
		{
			callUpdateRpc(bean, callback);
		}
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// ABSTRACT METHODS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	protected abstract void callRetrieveModelForEditionRpc(STORETYPE bean, DkBaseAsyncCallback<BEANTYPE> callback);

	protected abstract void callRetrieveModelForCreationRpc(DkBaseAsyncCallback<BEANTYPE> callback);

	protected abstract void callCreateRpc(BEANTYPE bean, DkBaseAsyncCallback<BEANTYPE> callback);

	protected abstract void callUpdateRpc(BEANTYPE bean, DkBaseAsyncCallback<BEANTYPE> callback);

	/**
	 * For more information about GWT Editor :<br>
	 * <a href="https://developers.google.com/web-toolkit/doc/latest/DevGuideUiEditors">
	 * https://developers.google.com/web-toolkit/doc/latest/DevGuideUiEditors</a>
	 *
	 * @return the editor driver
	 */
	protected abstract DkSimpleBeanEditorDriver<BEANTYPE, EDITORTYPE> getDriver();

	/**
	 * May be overridden if you need an action to convert the result data (you can get it within you action using
	 * {@link DkBaseAction#getPreviousActionResult()}). This action will be added to the chain after save Action.
	 *
	 * @return null if not needed, an action otherwise.
	 */
	protected DkBaseAction<STORETYPE> getConvertResultAction()
	{
		return null;
	}

	/**
	 * Called by {@link #showForCreation(ListStore)} after setting the store. Factor code for constructor with ListStore
	 * and TreeStore
	 *
	 * @return the {@link DkBaseAction} to be executed prior to show popup for creation
	 */
	protected DkBaseAction<BEANTYPE> getActionRetrieveModelForCreation()
	{
		return new DkBaseAction<BEANTYPE>(DkBaseMain.i18n().action_retrieveInstanceForCreation_loading_label())
		{
			@Override
			protected void run()
			{
				final DkBaseAsyncCallback<BEANTYPE> asyncCallback = new DkBaseAsyncCallback<BEANTYPE>(this, true)
				{
					@Override
					public void processSuccess(final BEANTYPE result)
					{
						if (result instanceof AbstractDkModifiableBeanWithId)
						{
							((AbstractDkModifiableBeanWithId) result).setCreation(true);
						}
						DkEditCreateProxyPopup.this.isCreation = true;
						DkEditCreateProxyPopup.this.index = 0;
						DkEditCreateProxyPopup.this.editedModel = result;
						DkEditCreateProxyPopup.this.inputModel = null;
						DkEditCreateProxyPopup.this.onBeforeShow();
						DkEditCreateProxyPopup.super.show();
					}
				};
				if (callback == null)
				{
					callRetrieveModelForCreationRpc(asyncCallback);
				}
				else
				{
					callback.callRetrieveModelForCreationRpc(DkEditCreateProxyPopup.this, asyncCallback);
				}
			}
		};
	}

	/**
	 * Called by {@link #showForUpdate(ListStore, AbstractDkBeanWithId)} after setting the store. Factor code for
	 * constructor with ListStore and TreeStore
	 *
	 * @param inputModel
	 *           the model to be edited. This action aims to retrieve the latest and full version of this model before
	 *           editing it.
	 * @return the {@link DkBaseAction} to be executed prior to show popup for edition
	 */
	protected DkBaseAction<BEANTYPE> getActionRetrieveModelForEdition(final STORETYPE inputModel)
	{
		return new DkBaseAction<BEANTYPE>(DkBaseMain.i18n().action_retrieveInstanceForEdition_loading_label())
		{
			@Override
			protected void run()
			{
				final DkBaseAsyncCallback<BEANTYPE> asyncCallback = new DkBaseAsyncCallback<BEANTYPE>(this, true)
				{
					@Override
					public void processSuccess(final BEANTYPE result)
					{
						DkEditCreateProxyPopup.this.isCreation = false;
						DkEditCreateProxyPopup.this.index = 0;
						DkEditCreateProxyPopup.this.treeParentModel = null;
						DkEditCreateProxyPopup.this.editedModel = result;
						DkEditCreateProxyPopup.this.inputModel = inputModel;
						DkEditCreateProxyPopup.this.onBeforeShow();
						DkEditCreateProxyPopup.super.show();
					}
				};
				if (callback == null)
				{
					callRetrieveModelForEditionRpc(inputModel, asyncCallback);
				}
				else
				{
					callback.callRetrieveModelForEditionRpc(DkEditCreateProxyPopup.this, inputModel, asyncCallback);
				}
			}
		};
	}

	/**
	 * May be overridden if you need an action to be executed right after saving (before
	 * {@link #getConvertResultAction()})
	 *
	 * @return a {@link DkBaseAction} or null
	 */
	protected DkBaseAction<BEANTYPE> getPostSaveAction()
	{
		return null;
	}

	/**
	 * May be overridden if you need an action to be executed before save is done (before
	 *
	 * @return
	 */
	protected DkBaseAction<?> getPreSaveAction()
	{
		return null;
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// PSEUDO EVENT MECHANISM
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Edit the model in the driver.<br>
	 * This method calls these methods in the given order:
	 * <ul>
	 * <li>{@link DkEditCreatePopupCallback#onBeforeDriverEdit(AbstractDkBeanWithId, DkSimpleBeanEditorDriver)}</li>
	 * <li>{@link #onBeforeDriverEdit()}</li>
	 * <li>{@link DkEditCreatePopupCallback#onAfterDriverEditBeforeShow(Editor, DkSimpleBeanEditorDriver)}</li>
	 * <li>{@link #onAfterDriverEditBeforeShow()}</li>
	 * </ul>
	 */
	protected void doDriverEdit()
	{
		setHeadingText(getLabelPopupTitle());
		getSetupDriver().accept(new DkClearValidationVisitor());
		if (callback != null)
		{
			callback.onBeforeDriverEdit(editedModel, getSetupDriver());
		}
		onBeforeDriverEdit();
		getSetupDriver().edit(null);
		getSetupDriver().edit(editedModel);
		if (callback != null)
		{
			callback.onAfterDriverEditBeforeShow(editor, getSetupDriver());
		}
		onAfterDriverEditBeforeShow();
	}

	/**
	 * Method called before model is edited in the driver and after
	 * {@link DkEditCreatePopupCallback#onBeforeDriverEdit(AbstractDkBeanWithId, DkSimpleBeanEditorDriver)} is called.
	 */
	protected void onBeforeDriverEdit()
	{
		// Nothing done by default.
	}

	/**
	 * Method called after model is edited in the driver and after
	 * {@link DkEditCreatePopupCallback#onAfterDriverEditBeforeShow(Editor, DkSimpleBeanEditorDriver)} is called.
	 */
	protected void onAfterDriverEditBeforeShow()
	{
		// Nothing done by default.
	}

	/**
	 * Method called before model is edited in the driver.<br>
	 * Before {@link #doDriverEdit()} and thus before {@link #onBeforeDriverEdit()}
	 */
	protected void onBeforeShow()
	{
		doDriverEdit();
	}

	/**
	 * Callback when model saved before store is updated.
	 *
	 * @param actionResult
	 *           the saved model returned from server. It may be null.
	 */
	public void onAfterSaveBeforeStoreUpdate(final STORETYPE actionResult)
	{
		// Nothing done by default
	}

	/**
	 * Callback when model saved after the store is updated. This method is not called if the actionResult is null.
	 *
	 * @param actionResult
	 *           the saved model returned from server. It cannot be be null.
	 */
	public void onAfterSaveAfterStoreUpdate(final STORETYPE actionResult)
	{
		// Nothing done by default
	}

	protected void onCancel()
	{
		// store.rejectChanges();
	}

	protected void onAfterSave(final STORETYPE storeTypeResult)
	{
		if (callback != null)
		{
			callback.onAfterSaveBeforeStoreUpdate(storeTypeResult, isCreation());
		}
		onAfterSaveBeforeStoreUpdate(storeTypeResult);
		if (storeTypeResult == null)
		{
			if (getStore() != null)
			{
				getStore().remove(inputModel);
			}
		}
		else
		{
			if (getStore() != null)
			{
				DkStoreUtils.addModelToListStore(getStore(), isCreation, storeTypeResult);
			}
			else if (getTreeStore() != null)
			{
				DkStoreUtils.addModelToTreeStore(getTreeStore(), isCreation, storeTypeResult, treeParentModel, createTreeStoreModifierCallback());
			}
		}
		if (callback != null)
		{
			callback.onAfterSaveAfterStoreUpdate(storeTypeResult, isCreation());
		}
		onAfterSaveAfterStoreUpdate(storeTypeResult);
		if (isHideAfterSave())
		{
			hide();
		}
	}

	/**
	 * Override this method to provide custom behavior for adding models to treestore after save or update
	 *
	 * @return a {@link TreeStoreModifierCallback}
	 * @see TreeStoreModifierCallback
	 */
	protected TreeStoreModifierCallback<STORETYPE> createTreeStoreModifierCallback()
	{
		return new TreeStoreModifierCallback<STORETYPE>();
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// CALLBACKS FROM UI
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	@Override
	protected void onButtonSaveClicked()
	{
		final Timer timer = new Timer()
		{
			@Override
			public void run()
			{
				getSaveChainAction().execute();
			}
		};
		timer.schedule(150);
	}

	protected DkBaseAction<?> getSaveChainAction()
	{
		DkBaseAction<?> action = getPreSaveAction();
		final DkBaseAction<BEANTYPE> actionSave = new DkBaseAction<BEANTYPE>(getLabelActionSaveLoadingLabel())
		{
			@Override
			protected void run()
			{
				getSetupDriver().accept(new DkPreFlushVisitor(true));
				final BEANTYPE updatedModel = getSetupDriver().flush();
				if (getSetupDriver().hasErrors())
				{
					showErrorPopup();
					stopChain();
					onSuccess();
				}
				else
				{
					callCreateOrUpdateRpc(updatedModel, new DkBaseAsyncCallback<BEANTYPE>(this, true)
					{
						@Override
						public void processSuccess(final BEANTYPE result)
						{
							// Nothing to be done. Process made in chain action.
						}
					});
				}
			}
		};
		if (action == null)
		{
			action = actionSave;
		}
		else
		{
			action.setLastChainAction(actionSave);
		}
		final DkBaseAction<BEANTYPE> actionPostSave = getPostSaveAction();
		if (actionPostSave != null)
		{
			action.setLastChainAction(actionPostSave);
		}
		DkBaseAction<STORETYPE> actionConvertResult = null;
		if (callback != null)
		{
			actionConvertResult = callback.getConvertResultAction();
		}
		if (actionConvertResult == null)
		{
			actionConvertResult = getConvertResultAction();
		}
		if (actionConvertResult != null)
		{
			action.setLastChainAction(actionConvertResult);
		}
		final DkBaseAction<Void> actionAfterSave = new DkBaseAction<Void>(DkBaseMain.i18n().action_update_screen_loading_label())
		{
			@SuppressWarnings("unchecked")
			@Override
			protected void run()
			{
				final STORETYPE previousActionResult = (STORETYPE) getPreviousActionResult();
				if (previousActionResult == null)
				{
					DkMessageBox.warn(getLabelPopupSaveReturnedNullMessageBoxTitle(), getLabelPopupSaveReturnedNullMessageBoxMessage(),
							new DkMessageBox.AfterCloseListener()
							{
								@Override
								public void afterClose()
								{
									onAfterSave(previousActionResult);
								}
							});
				}
				else
				{
					DkMessageBox.info(getLabelPopupSaveOkMessageBoxTitle(), getLabelPopupSaveOkMessageBoxMessage(), new DkMessageBox.AfterCloseListener()
					{
						@Override
						public void afterClose()
						{
							onAfterSave(previousActionResult);
						}
					});
				}
				onSuccess();
			}
		};
		action.setLastChainAction(actionAfterSave);
		return action;
	}

	@Override
	protected void onButtonCancelClicked()
	{
		onCancel();
		hide();
	}

	@Override
	protected void onButtonResetClicked()
	{
		getSetupDriver().edit(editedModel);
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// LABELS METHODS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	@Override
	protected String getLabelPopupTitle()
	{
		return isCreation() ? DkBaseMain.i18n().popup_create_or_edit_title_create(getBeanLabel()) : DkBaseMain.i18n().popup_create_or_edit_title_edit(
				getBeanLabel());
	}

	/**
	 * @return the label used for save action progress dialog
	 */
	protected String getLabelActionSaveLoadingLabel()
	{
		return DkBaseMain.i18n().action_save_loading_label(getBeanLabel());
	}

	/**
	 * @return the label used for save confirmation message box title
	 */
	protected String getLabelPopupSaveOkMessageBoxTitle()
	{
		return DkBaseMain.i18n().popup_create_or_edit_save_popup_ok_title(getBeanLabel());
	}

	/**
	 * @return the label used for save confirmation message box message
	 */
	protected String getLabelPopupSaveOkMessageBoxMessage()
	{
		return DkBaseMain.i18n().popup_create_or_edit_save_popup_ok_message(getBeanLabel());
	}

	/**
	 * @return the label used for save confirmation message box title
	 */
	protected String getLabelPopupSaveReturnedNullMessageBoxTitle()
	{
		return isCreation() ? DkBaseMain.i18n().popup_create_save_popup_returned_null_title(getBeanLabel()) : DkBaseMain.i18n()
				.popup_edit_save_popup_returned_null_title(getBeanLabel());
	}

	/**
	 * @return the label used for save confirmation message box message
	 */
	protected String getLabelPopupSaveReturnedNullMessageBoxMessage()
	{
		return isCreation() ? DkBaseMain.i18n().popup_create_save_popup_returned_null_message(getBeanLabel()) : DkBaseMain.i18n()
				.popup_edit_save_popup_returned_null_message(getBeanLabel());
	}

	/**
	 * @return the bean label to be used in dialog box titles / messages / action progress bars ...
	 */
	protected abstract String getBeanLabel();

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// GETTERS / SETTERS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	@Override
	public final void invalidateUi()
	{
		super.invalidateUi();
		onInvalidateUi();
		if (callback != null)
		{
			callback.onAfterInvalidateUi(getEditor());
		}
	}

	protected abstract void onInvalidateUi();

	/**
	 * Create (the first time) and get the driver.
	 *
	 * @return the edition driver
	 */
	@SuppressWarnings("unchecked")
	protected DkSimpleBeanEditorDriver<BEANTYPE, EDITORTYPE> getSetupDriver()
	{
		if (driver == null)
		{
			driver = getDriver();
			driver.initialize(editor);
		}
		return driver;
	}

	/**
	 * @return the {@link ListStore}
	 */
	protected ListStore<? super STORETYPE> getStore()
	{
		return this.store;
	}

	/**
	 * @return the {@link TreeStore}
	 */
	protected TreeStore<STORETYPE> getTreeStore()
	{
		return this.treeStore;
	}

	/**
	 * @return whether current action is creation or edition
	 */
	protected boolean isCreation()
	{
		return isCreation;
	}

	/**
	 * @return the editor
	 */
	public EDITORTYPE getEditor()
	{
		return editor;
	}

	/**
	 * @return the model
	 */
	public BEANTYPE getEditedModel()
	{
		return editedModel;
	}

	/**
	 * The input Model (before being converted to BEANTYPE thanks to
	 * #getActionRetrieveModelForEdition(AbstractDkBeanWithId)
	 *
	 * @return the inputModel. It is null if it is a creation.
	 */
	public STORETYPE getInputModel()
	{
		return inputModel;
	}

	/**
	 * @return the hideAfterSave
	 */
	public boolean isHideAfterSave()
	{
		return hideAfterSave;
	}

	/**
	 * @param hideAfterSave
	 *           the hideAfterSave to set
	 */
	public void setHideAfterSave(final boolean hideAfterSave)
	{
		this.hideAfterSave = hideAfterSave;
	}
}
